/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import Dep, { pushTarget, popTarget } from '../observer/dep'
import { isUpdatingChildComponent } from './lifecycle'

import {
  set,
  del,
  observe,
  defineReactive,
  toggleObserving
} from '../observer/index'

import {
  warn,
  bind,
  noop,
  hasOwn,
  hyphenate,
  isReserved,
  handleError,
  nativeWatch,
  validateProp,
  isPlainObject,
  isServerRendering,
  isReservedAttribute
} from '../util/index'

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
// initState初始化顺序：props -> methods -> data -> computed -> watch
export function initState (vm: Component) {
  // vm._watchers：所有的watcher数组
  // vm._computedWatchers：计算属性watcher
  // vm._watcher：组件渲染watcher
  vm._watchers = []
  const opts = vm.$options
  // 初始化props，主要包括: props校验、响应式和代理
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {  // 设置data选项的响应式
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 计算属性watcher
  if (opts.computed) initComputed(vm, opts.computed)
  // 用户自定义watcher
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

// 初始化props，主要包括props校验、响应式和代理
function initProps(vm: Component, propsOptions: Object) {
  // propsData：从父组件传递过来的props数据
  // propsData：在"initInternalComponent"方法内部，已经将vnode的componentOptions.propsData放到了当前vm.$options上
  const propsData = vm.$options.propsData || {}
  // vm._props，存放所有props键值对
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 缓存所有的props key
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {  // 非根组件的props，不需要响应式的
    /**
     * toggleObserving(false)原因：
     * 1) 对于对象的 prop 值，子组件的 prop 值始终指向父组件的 prop 值（传递过来的值就是响应式的）。
     *      所以在defineReactive的时候,只做了一层defineReactive，没有递归做响应式。即，toggleObserving(false)，此时，执行defineReactive时，对于childObj，就不会执行observer方法了。
     *      所以，当props[key]的值是一个对象（或者数组时），后续的这个 observe 过程是可以省略的。
     * 2) validateProp时，如果value没有传递默认值，则需要对value进行观察,即toggleObserving(true)，完成后，恢复默认。
     */
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    // 1) 校验传递的propsData中的值，是否满足propsOptions中定义的规范
    // 2) 如果没有传值，则对默认的prop的值进行observe(定义响应式) vm.propsA = {a: 1}, a变化时，页面更新
    const value = validateProp(key, propsOptions, propsData, vm); // 获取并验证props的`value`
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      // 定义props的响应式
      // 在 defineReactive 的时候会添加一个自定义 setter(customSetter)，当我们直接对 prop 的某个key赋值的时候会输出警告(不允许修改props)
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          // Vue自身更新props不报错，是因为在执行vnode.prepatch -> updateChildComponent时，设置了isUpdatingChildComponent为true
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 2) 定义props的key响应式, 并将key，value添加到vm._props中,即:vm._props.key = value;
      // props的key定义为响应式后，这样在取值时，就可以收集依赖了
      // 修改vm.propsA = {b: 2},即propsA变化时，页面更新
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype during Vue.extend(). We only need to proxy props defined at instantiation here.
    // 静态props已经在Vue.extend执行期间被代理到component原型上了，在这里，我们仅仅需要代理那些在安装阶段定义的props(动态props)
    if (!(key in vm)) {
      // 3) 代理vm.key的访问，即访问vm.key，等同于vm._props.key。所以，可以直接通过vm实例，访问到props的值
      // 原理就是通过Object.defineProperty(target, key, sharedPropertyDefinition)重写key的getter、setter
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 监测props、methods中是否有跟data中重名的key
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) { // 如果是保留字(以'$'、'_'开头的变量)，则不做代理
      // 代理vm.key的访问，即访问vm.key时，就是访问vm._data.key
      // 原理就是通过Object.defineProperty(target, key, sharedPropertyDefinition)重写key的getter、setter
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  // vm._computedWatchers，存储计算属性watcher
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    // computed的写法，一种是fn；一种是对象，包括get、set方法
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      // 给每一个计算属性，创建一个计算属性watcher
      // 计算属性watcher, lazy为true，getter为计算属性key所对应的function
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the component prototype. 
    // We only need to define computed properties defined at instantiation here.
    // 静态computed属性已经在Vue.extend执行期间被代理到component的原型上了，在这里，我们仅仅需要代理那些在安装阶段定义的computed属性
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // 非server-render条件下，'shouldCache'为true
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    // key的值为function,例如: computed: { fullname: function() {} }
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    // computed方式为自定义getter、setter: 例如fullname: {get: function(){}, set: function(){} };
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 通过Object.defineProperty,定义vm.key的descriptor为`sharedPropertyDefinition`
  // 它的get方法为'createComputedGetter'的返回值，即'computedGetter'函数
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 对computed的key取值时，触发createComputedGetter
function createComputedGetter (key) {
  // 计算属性中，某个key的get方法，就是’computedGetter‘函数，对计算属性取值时，就会调用get方法(即’computedGetter‘函数)
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 默认计算属性watcher的dirty为true，第一次计算时，调用watcher.evaluate计算值。
      if (watcher.dirty) {
        // evaluate内部会调用get方法，执行getter函数来获取最新的值，并进行依赖收集，同时将watcher的dirty设置为false
        /**
         * 详细过程如下：computed: { fullName :function() { this.first + this.last }}
         * 1. 先创建'渲染watcher'，创建vnode，此时渲染'watcher'先入栈, targetStack = [渲染watcher]
         * 2、创建vnode时，对fullName取值，触发computedGetter方法
         * 2、如果"当前计算属性watcher(fullName_watcher)"的dirty为true(默认是true)，执行watcher.evaluate方法
         *    1）执行watcher的"get"方法，设置Dep.target为"fullName_watcher"，同时将"fullName_watcher"入栈,此时targetStack = [渲染watcher, fullName_watcher]。
         *      然后调用getter方法开始取first、last的值，触发各自的get方法进行依赖收集。此时first_dep，last_dep会收集"fullName_watcher"的依赖，“fullName_watcher”也会收集这2个dep。
         *        1：计算属性watcher中将first、last的dep收集起来，放到自己的newDeps数组中。即fullName_Watcher的newDeps: [first_Dep, last_Dep]
         *        2：first_Dep,last_Dep这两个dep,分别收集当前计算属性watcher,放到各自的subs数组中。即subs:[fullName_Watcher]
         * 3、依赖收集完成后，fullName_watcher出栈, 并将dirty设置为false，此时targetStack = [渲染watcher]。
         */
        watcher.evaluate(); // 计算属性watcher，记住first_Dep,last_Dep这2个dep
      }
      // 当fullName_watcher出栈后，当前'Dep.target'为渲染watcher.
      if (Dep.target) { // 让计算属性所依赖的属性，收集渲染watcher
        /**
         * 循环当前watcher的deps属性[first_Dep,last_Dep],执行dep.depend()进行依赖收集,由于当前'Dep.target'为渲染watcher，
         * 因此first_Dep,last_Dep会收集渲染watcher，同时渲染watcher也会收集这2个dep。
         * 
         * 当我们修改first、last的值时，触发dep.notify -> watcher.update() -> queneWatcher -> flushSchedulerQueue -> watcher.run, 页面重新渲染, 获取最新的计算值
         *  1) 注意，当watcher为'计算属性watcher时(lazy为true)'，在执行'watcher.update'时，只是将当前watcher的dirty设置为true，并不会加入queneWatcher队列。
         *  2) 此时queneWatcher队里中只有渲染watcher。当渲染watcher执行时，对vm.fullName取值，执行computedGetter函数，如果dirty为true，重新计算watcher的value值。
         */

        // 注意：此时'watcher.depend'中的watcher，是'当前计算属性watcher'，内部收集了2个dep:[first_Dep,last_Dep]
        // 调用watcher.depend时，让这2个dep分别收集渲染watcher，当first_Dep或last_Dep发生变化时，通知渲染watcher更新(重新渲染页面)
        watcher.depend();  // 目的：让渲染watcher，记住first_Dep,last_Dep这2个dep，以便在值变化时，通知渲染watcher更新
      }
      return watcher.value
    }
  }
}

function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}
// 给每一个属性(key)，创建一个watcher
function initWatch(vm: Component, watch: Object) {
  // 给每一个key创建watcher
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  // watcher的key是对象的写法: key: { handler: fn, deep: true, immediate: true }
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  // handler是字符串的写法： key:'someMethod'
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  // 调用$watch方法(stete.js 383行)
  return vm.$watch(expOrFn, handler, options)
}

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      // 用户直接调用,例如vm.$watch('name',{ handler: fn, deep: true, immediate: true })
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 用户watcher
    options.user = true
    // 用户watcher的"expOrFn",就是是watch选项的key，以'.'分隔, 类似'a.b.c'
    // cb就是watch对应的handler
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) { // 初始化用户watcher时，执行callback
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}
